/** mutex.c: Implements muteces.
 *
 * Author: 	Nora Tarano <ntarano@andrew.cmu.edu>
 *          Yuan Meng <yuanm@andrew.cmu.edu>
 *          Yipeng Yun <yipengy@andrew.cmu.edu>
 * Date: 	Dec 11, 2011
 */

#include <types.h>
#include <lock.h>
#include <task.h>
#include <sched.h>

#include <bits/errno.h>

#include <arm/psr.h>
#include <arm/exception.h>

#ifdef DEBUG_MUTEX
#include <exports.h> // temp
#endif

#define EXIT_SUCCESS 0

mutex_t mutexes[OS_NUM_MUTEX];
static int curr_num_mutex;	// keep track of the the number of mutexes created
static int mutexes_initialized = 0;	// check if all mutexes are initialized

// initializes all mutexes
// only needs to be done once at the beginning
void mutex_init()
{
	int i;
	for (i = 0; i < OS_NUM_MUTEX; i++)
	{
		mutexes[i].bAvailable = 1;
		mutexes[i].pHolding_Tcb = 0;
		mutexes[i].bLock = 0;
		mutexes[i].pSleep_queue = 0;
	}
	// no mutex has been created
	curr_num_mutex = 0;
}

// create a mutex
// returns the index of the created mutex
int mutex_create(void)
{
	// check if mutexes have been initialized
	if (!mutexes_initialized)
	{
		mutex_init();
		mutexes_initialized = 1;
	}

	// check if the number of mutex has eceeded OS_NUM_MUTEX
	if (curr_num_mutex < OS_NUM_MUTEX)
	{
		mutexes[curr_num_mutex].bAvailable = 0;
		return curr_num_mutex++;
	}
	else
		return -ENOMEM;
}

// acquire(lock) the indicated mutex
int mutex_lock(int mutex)
{
	tcb_t* curr_task = get_cur_tcb();

	disable_interrupts();

	// check if the mutex index is valid
	if (mutex < 0 || mutex >= curr_num_mutex)
	{
		enable_interrupts();
		return -EINVAL;
	}
	// check for deadlocks
	else if (mutexes[mutex].pHolding_Tcb == curr_task)
	{
		enable_interrupts();
		return -EDEADLOCK;
	}
	else
	{
		// wait for the mutex to be avaliable
		while (mutexes[mutex].bLock)
		{
			// add task the sleep queue
			curr_task->sleep_queue=mutexes[mutex].pSleep_queue;
			mutexes[mutex].pSleep_queue=curr_task;
			dispatch_sleep();
		}

		// provide mutex to the task
		mutexes[mutex].bLock = 1;
		mutexes[mutex].pHolding_Tcb = curr_task;
		curr_task->cur_prio = 0;
		curr_task->holds_lock = 1;

		enable_interrupts();
		return EXIT_SUCCESS;
	}
}

// release(unlock) the indicated mutex
int mutex_unlock(int mutex)
{
	tcb_t* curr_task = get_cur_tcb();

	disable_interrupts();

	// check if mutex is being used by the current task
	if (mutexes[mutex].pHolding_Tcb != curr_task)
	{
		enable_interrupts();
		return -EINVAL;
	}
	// check if the mutex index is valid
	else if ((mutex < 0) || (mutex >= curr_num_mutex))
	{
		enable_interrupts();
		return -EINVAL;
	}
	else
	{
		// transfer the sleeping que of the mutex into the run queue
		tcb_t* temp = mutexes[mutex].pSleep_queue;
		while (temp != 0)
		{
			runqueue_add(temp, temp->native_prio);
			temp = temp->sleep_queue;
		}

		// reset the mutex
		mutexes[mutex].pSleep_queue = 0;
		mutexes[mutex].bLock = 0;
		mutexes[mutex].pHolding_Tcb = 0;

		// check if the task hold more mutexes
		int more_mutexes = 0, i = 0;
		while ((more_mutexes == 1) && (i < curr_num_mutex))
		{
			if (mutexes[i].pHolding_Tcb == curr_task)
				more_mutexes = 1;
			i++;
		}

		if (!more_mutexes)
		{
			curr_task->holds_lock = 0;
			curr_task->cur_prio = curr_task->native_prio;
		}
		
		enable_interrupts();
		return EXIT_SUCCESS;
	}
}
